// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: gekim

#pragma once

#include <string>

#include <rapidjson/document.h>
#include <rapidjson/rapidjson.h>

#include "coin/proto/coin_market_enums.pb.h"
#include "coin2/base/conversion.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed_util/book_builder.h"
#include "coin2/exchange/base/feed_util/trade.h"

namespace impl {
using coin2::exchange::base::market::ExchangeType;
using coin2::exchange::base::market::MarketType;

int64_t ts_parse(std::string str_time) {
  int64_t ts = TimestampFromIso8601(str_time + "Z");
  ts -= 28800000000000LL;  // Beijing Time -> UTC
  return ts;
}

class LbankFeedUpdateParser {
 public:
  static void ParseBook(
      const rapidjson::Value& book_json,
      BookBuilder* book_builder,
      int64_t ts,
      BookBuilder::BookOption book_type) {
    auto tx = book_builder->NewTransaction(ts);
    if (book_type == BookBuilder::SNAPSHOT) {
      tx.Clear();
    }

    if (book_json.HasMember("asks")) {
      for (const auto& ask : book_json["asks"].GetArray()) {
        tx.UpdateAsk(stod(ask[0].GetString()), stod(ask[1].GetString()));
      }
    }
    if (book_json.HasMember("bids")) {
      for (const auto& bid : book_json["bids"].GetArray()) {
        tx.UpdateBid(stod(bid[0].GetString()), stod(bid[1].GetString()));
      }
    }
  }

  static void ParseTrade(const rapidjson::Value& trade_json, Trade* trade) {
    trade->price = stod(trade_json["price"].GetString());
    trade->fill_qty = std::stod(trade_json["amount"].GetString());
    trade->side = ParseTradeSide(trade_json);
    trade->timestamp = ts_parse(trade_json["TS"].GetString());
  }

  static coin2::exchange::feed::TradeSide ParseTradeSide(const rapidjson::Value& trade_json) {
    bool is_buy = strcmp(trade_json["direction"].GetString(), "buy") == 0;
    if (is_buy) {
      return coin2::exchange::feed::TRADE_BUY_SIDE;
    } else {
      return coin2::exchange::feed::TRADE_SELL_SIDE;
    }
  }

  static bool Parse1Kline(
      const rapidjson::Value& kline_data,
      Klines* klines,
      int64_t timestamp,
      fastfeed::proto::KlineInterval interval) {
    int64_t open_timestamp = ts_parse(kline_data["t"].GetString());
    auto result = klines->AddKline(open_timestamp);
    if (!result) return false;

    Kline* kline;
    bool freezed;
    std::tie(kline, freezed) = result.value();

    kline->open = stod(kline_data["o"].GetString());
    kline->high = stod(kline_data["h"].GetString());
    kline->low = stod(kline_data["l"].GetString());
    kline->close = stod(kline_data["c"].GetString());
    kline->volume = stod(kline_data["v"].GetString());
    kline->timestamp = timestamp;
    kline->interval = interval;

    return freezed;
  }

  static fastfeed::proto::KlineInterval ParseKlineInterval(const std::string& channel) {
    static std::map<std::string, fastfeed::proto::KlineInterval> interval_map = {
        {"kline_ws_1min", fastfeed::proto::KLINE_INTERVAL_1MIN},
        {"kline_ws_5min", fastfeed::proto::KLINE_INTERVAL_5MIN},
        {"kline_ws_15min", fastfeed::proto::KLINE_INTERVAL_15MIN},
        {"kline_ws_1hour", fastfeed::proto::KLINE_INTERVAL_1HOUR},
        {"kline_ws_1day", fastfeed::proto::KLINE_INTERVAL_1DAY},
        {"minute1", fastfeed::proto::KLINE_INTERVAL_1MIN},
        {"minute5", fastfeed::proto::KLINE_INTERVAL_5MIN},
        {"minute15", fastfeed::proto::KLINE_INTERVAL_15MIN},
        {"hour1", fastfeed::proto::KLINE_INTERVAL_1HOUR},
        {"day1", fastfeed::proto::KLINE_INTERVAL_1DAY},
    };
    auto iter = interval_map.find(channel);
    if (iter == interval_map.end()) {
      return fastfeed::proto::KLINE_INTERVAL_INVALID;
    }

    return iter->second;
  }

  static bool ParseKlineRest(
      const rapidjson::Value& msg,
      Klines* klines,
      fastfeed::proto::KlineInterval interval,
      int64_t timestamp) {
    int64_t open_timestamp = stoll(msg[0].GetString()) * 1'000'000'000LL;
    auto result = klines->AddKline(open_timestamp);
    if (!result) return false;

    Kline* kline;
    bool freezed;
    std::tie(kline, freezed) = result.value();
    kline->open_timestamp = open_timestamp;
    kline->open = stod(msg[1].GetString());
    kline->high = stod(msg[2].GetString());
    kline->low = stod(msg[3].GetString());
    kline->close = stod(msg[4].GetString());
    kline->volume = stod(msg[5].GetString());
    kline->timestamp = timestamp;
    kline->interval = interval;

    return freezed;
  }
};
}  // namespace impl

using impl::LbankFeedUpdateParser;
